package com.bjb.vr.client.task;

import cn.hutool.core.lang.UUID;
import cn.hutool.crypto.digest.HMac;
import cn.hutool.crypto.digest.HmacAlgorithm;
import cn.hutool.crypto.digest.MD5;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.bjb.vr.client.properties.ServerInfoProperties;
import com.bjb.vr.client.utils.HttpSender;
import com.bjb.vr.common.constant.CanalConstant;
import com.bjb.vr.common.constant.EncryptConstant;
import com.bjb.vr.common.constant.ServerModeConstant;
import com.bjb.vr.common.constant.SyncDataConstant;
import com.bjb.vr.common.dto.ExecutedSql;
import com.bjb.vr.common.service.ExecutedSqlService;
import com.bjb.vr.common.utils.CanalTool;
import com.bjb.vr.common.utils.DateTimeUtil;
import com.bjb.vr.common.utils.OkHttpUtil;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Call;
import org.apache.http.HttpStatus;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;

/**
 * @description:
 * @author: HuJingBo
 * @time: 2022/7/27 13:30
 */
@Slf4j
@Component
public class SyncDataTask {

    @Resource
    private ServerInfoProperties serverInfoProperties;

    @Resource
    private JdbcTemplate jdbcTemplate;

    @Resource
    private ExecutedSqlService executedSqlService;

    /**
     * 每隔2秒钟，同步云端数据到本地
     * 【云服务不需要此操作】
     */
    @Async
    @Scheduled(fixedDelay = 2000, initialDelay = 5000)
    public void syncCloudDataToLocalTask() {
        // 如果是云端服务，直接跳过
        if (ServerModeConstant.LOCAL.equals(serverInfoProperties.getDeployMode())) {
            // 根据配置文件变化，动态执行任务
            if (serverInfoProperties.getNeedSyncDatabase()) {
                // 通知云端服务授权
                // 执行同步任务操作
                handleSyncData();
            }
        }
    }

    /**
     * 从云端获取同步数据
     */
    public void handleSyncData() {
        // 1、获取请求数据
        String tenantId = serverInfoProperties.getTenantId();
        String requestId = UUID.fastUUID().toString();
        String requestTime = DateTimeUtil.nowStrTime();
        StringBuilder builder = new StringBuilder();
        String data = builder.append(tenantId).append(requestId).append(requestTime).toString();
        byte[] key = EncryptConstant.ENCRYPT_KEY.getBytes();
        HMac mac = new HMac(HmacAlgorithm.HmacMD5, key);
        String signature = mac.digestHex(data);

        // 2、封装请求数据
        String syncDataUrl = serverInfoProperties.getSyncDataUrl();
        HashMap<String, String> params = new HashMap<>();
        params.put("tenantId", tenantId);
        params.put("requestId", requestId);
        params.put("requestTime", requestTime);
        params.put("signature", signature);

        // 3、发送请求
        OkHttpUtil httpUtil = OkHttpUtil.builder().url(syncDataUrl).addHeader("Content-Type", "application/json; charset=utf-8");
        Iterator<Map.Entry<String, String>> iterator = params.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, String> next = iterator.next();
            httpUtil.addParam(next.getKey(), next.getValue());
        }

        httpUtil.post(true).async(new OkHttpUtil.ICallBack() {
            @Override
            public void onSuccessful(Call call, String data) {
                JSONObject object = JSONObject.parseObject(data);
                if (object.containsKey(SyncDataConstant.STATUS) && HttpStatus.SC_NOT_FOUND == (Integer) object.get(SyncDataConstant.STATUS)) {
                    log.error("从云端服务器获取数据失败！| 原因: {}", object.get(SyncDataConstant.ERROR));
                    return;
                }
                if (object.containsKey(SyncDataConstant.CODE) && String.valueOf(HttpStatus.SC_INTERNAL_SERVER_ERROR).equals(object.get(SyncDataConstant.CODE))) {
                    log.error("从云端服务器获取数据失败！| 原因: {}", object.get(SyncDataConstant.MESSAGE));
                    return;
                }
                if (object.containsKey(SyncDataConstant.CODE) && String.valueOf(HttpStatus.SC_UNAUTHORIZED).equals(object.get(SyncDataConstant.CODE))) {
                    log.error("从云端服务器获取数据失败！| 原因: {}", object.get(SyncDataConstant.MESSAGE));
                    return;
                }

                // 获取数据成功
                JSONArray dataArray = (JSONArray) object.get(SyncDataConstant.DATA);
                if (dataArray.size() > 0) {
                    log.debug("从云端服务器获取[{}]条【同步数据】", dataArray.size());
                    saveStatement(dataArray);
                }
            }

            @Override
            public void onFailure(Call call, String errorMsg) {
                log.error("请检查网络连通情况...... {}", syncDataUrl);
            }
        });
    }

    /**
     * 执行云端同步数据
     *
     * @param dataArray
     */
    public void saveStatement(JSONArray dataArray) {
        if (dataArray.size() == 0) {
            return;
        }
        try {
            // 1、执行云端数据
            StringBuilder removeIds = new StringBuilder();
            StringBuilder removeMd5s = new StringBuilder();
            for (int i = 0; i < dataArray.size(); i++) {
                JSONObject object = dataArray.getJSONObject(i);
                String statementSql = object.getString(CanalConstant.STATEMENT);
                String tableName = object.getString("tableName");
                String eventType = object.getString(CanalConstant.EVENT_TYPE);
                String dataId = object.getString("dataId");
                String statementMd5 = MD5.create().digestHex(statementSql);
                // 封装删除同步数据IDS
                if (i != dataArray.size() - 1) {
                    removeIds.append(object.get(CanalConstant.ID).toString()).append(",");
                    removeMd5s.append(statementMd5).append(",");
                } else {
                    removeIds.append(object.get(CanalConstant.ID).toString());
                    removeMd5s.append(statementMd5);
                }

                String tenantId = object.getString("tenantId");
                object.put(CanalConstant.DB, object.getString("dbName"));
                object.put(CanalConstant.TABLE, object.getString("tableName"));

                // 首先查询一下executed_sql表，检查有没有执行过该SQL，如果有删除即可。
                ExecutedSql executedSql = executedSqlService.queryByMd5(statementMd5, tenantId);
                if (Objects.nonNull(executedSql)) {
                    log.info("已经执行过该SQL,不再重复执行,SQL语句MD5值: {}", statementMd5);
                    // 执行删除操作
                    executedSqlService.deleteByMd5(statementMd5, tenantId);
                    return;
                }

                try {
                    // 执行传入过来的SQL语句
                    log.info("【服务端-->客户端】: " + statementSql);
                    jdbcTemplate.execute(statementSql);
                } catch (DataAccessException e) {
                    log.error("数据执行异常--> 表名: {} 操作: {} 数据ID: {} 错误信息: {}", tableName, eventType, dataId, e.getCause().getMessage());
                }

                try {
                    // 保存数据到executed_sql中
                    ExecutedSql executedEntity = CanalTool.buildExecuteEntity(object, statementSql, tenantId);
                    executedSqlService.saveDataToExecute(executedEntity);
                } catch (DataAccessException e) {
                }
            }

            // 通知云端删除执行之后数据
            callbackCloudRemoveCompletedData(removeIds.toString(), removeMd5s.toString());
        } catch (Exception e) {
            log.error("数据同步发生错误: {}", e);
        }
    }

    /**
     * 回调给云端同步完成之后的数据
     */
    public void callbackCloudRemoveCompletedData(String removeIds, String removeMd5s) throws Exception {
        // 1、封装请求数据
        String tenantId = serverInfoProperties.getTenantId();
        String requestId = UUID.fastUUID().toString();
        String requestTime = DateTimeUtil.nowStrTime();
        StringBuilder builder = new StringBuilder();
        String data = builder.append(tenantId).append(requestId).append(requestTime).toString();
        byte[] key = EncryptConstant.ENCRYPT_KEY.getBytes();
        HMac mac = new HMac(HmacAlgorithm.HmacMD5, key);
        String signed = mac.digestHex(data);

        HashMap<String, String> params = new HashMap<>();
        params.put("removeIds", removeIds);
        params.put("removeMd5s", removeMd5s);
        params.put("tenantId", tenantId);
        params.put("requestId", requestId);
        params.put("requestTime", requestTime);
        params.put("signature", signed);

        // 发送http请求
        HttpSender.postSync(serverInfoProperties.getRemoveSyncDataUrl(), params);
    }
}
